/*
 * Copyright (c) 2012 Sean Connelly
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//
// This function will traverse the AST and mark certain nodes with a 'scopeIndex' data value.
// rho_obj.scopeList[scopeIndex] is an object that maintains the scope tree via parent/children
// relationships.
//
// The root of the scope tree is rho_obj.scopeTree.
//
// This function requires that a node in the AST that generates a new scope must have that scope
// apply to all its children.  This requires some special planning when generating the AST.
//
// For example, suppose you have this code:
//
// try
//   int x: ask("Age?")
// carry
//   say(x)
// catch (var ex)
//   say("fail")
// end
//
// Suppose this generates an AST like this:
//
// TryCarryCatchBlock
//   TryStatementList
//     NewVariable(x, Call(ask, "Age?"))
//   CarryStatementList
//     Call(say, x)
//   CatchParameters(var, ex)
//   CatchStatementList
//     Call(say, "fail")
//
// This AST design is invalid, because the `x` should be scoped to the carry block, but NOT to the
// catch block.  There is no way to annotate the above AST in such a way that only TryStatementList
// and CarryStatementList share a scope, but Catch doesn't.
//
// For this reason, the AST needs a bit more refinement:
//
// TryCarryCatchBlock
//   TryCarryBlock                                 <-- mark new scope
//     TryStatementList
//       NewVariable(x, Call(ask, "Age?"))
//     CarryStatementList
//       Call(say, x)
//   CatchBlock                                    <-- mark new scope
//     CatchParameters(var, ex)
//     CatchStatementList
//       Call(say, "fail")
//
// This AST design will work for marking the scope, because the TryCarryBlock node can be marked
// with a new scope (that applies to all the children, TryStatementList and CarryStatementList),
// and CatchBlock can be marked with a separate scope, that does not overlap with TryCarryBlock.
//

function new_scope(name, parent, index)
{
	return {
			name: name,
			parent: parent,
			index: index,
			idents: {},
			children: []
		};
}

exports.run = function(rho_obj) {
		rho_obj.scopeTree = new_scope('file', false, 0);
		rho_obj.scopeList = [rho_obj.scopeTree];

		var cur_scope = rho_obj.scopeTree;
		var name_count = {};
		function push_scope(name)
		{
			if (typeof name_count[name] == 'undefined')
				name_count[name] = 0;
			name_count[name]++;
			name += '[' + name_count[name] + ']';
			cur_scope.children.push(new_scope(name, cur_scope, rho_obj.scopeList.length));
			cur_scope = cur_scope.children[cur_scope.children.length - 1];
			rho_obj.scopeList.push(cur_scope);
			return rho_obj.scopeList.length - 1;
		}

		function pop_scope()
		{
			cur_scope = cur_scope.parent;
		}

		var obj_each = function() {
				switch (this.getName())
				{
				case 'IfBlock':
					var sid = push_scope('if');

					this.toChild('ConditionList').each(function(idx) {
							if (idx > 0)
							{
								pop_scope();
								sid = push_scope('elseif');
							}
							this.updateData({ scopeIndex: sid })
								.each(obj_each);
						});

					if (this.hasChild('Else'))
					{
						pop_scope();
						this.toChild('Else')
							.updateData({ scopeIndex: push_scope('else') })
							.each(obj_each);
					}

					pop_scope();
					break;
				case 'FunctionLiteral':
					this.updateData({ scopeIndex: push_scope('code') })
						.each(obj_each);
					pop_scope();
					break;
				case 'DoBlock':
					this.updateData({ scopeIndex: push_scope('do') })
						.each(obj_each);
					pop_scope();
					break;
				case 'DoWhileBlock':
					this.updateData({ scopeIndex: push_scope('do') })
						.toChild('StatementList')
						.each(obj_each)
						.toParent()
						.toChild('Condition')
						.updateData({ scopeIndex: push_scope('while') })
						.each(obj_each);
					pop_scope();
					pop_scope();
					break;
				case 'WhenBlockPlain':
					this.toChild('IsList')
						.each(function() {
								this.updateData({ scopeIndex: push_scope('when-is') })
									.each(obj_each);
								pop_scope();
							});
					if (this.hasChild('Else'))
					{
						this.toChild('Else')
							.updateData({ scopeIndex: push_scope('when-else') })
							.each(obj_each);
						pop_scope();
					}
					break;
				case 'WhenBlockTyped':
					this.toChild('IsList')
						.each(function() {
								this.updateData({ scopeIndex: push_scope('when-is') })
									.each(obj_each);
								pop_scope();
							});
					pop_scope();
					break;
				case 'SwitchBlockPlain':
					this.toChild('CaseList')
						.each(function() {
								this.updateData({ scopeIndex: push_scope('switch-case') })
									.each(obj_each);
								pop_scope();
							});
					if (this.hasChild('Else'))
					{
						this.toChild('Else')
							.updateData({ scopeIndex: push_scope('switch-else') })
							.each(obj_each);
						pop_scope();
					}
					break;
				case 'TryCatchBlock':
					this.toChild('TryBlock')
						.updateData({ scopeIndex: push_scope('try') })
						.each(obj_each);
					pop_scope();
					this.toChild('CatchBlock')
						.updateData({ scopeIndex: push_scope('catch') })
						.each(obj_each);
					pop_scope();
					break;
				case 'TryCarryCatchBlock':
					this.toChild('TryCarryBlock')
						.updateData({ scopeIndex: push_scope('try-carry') })
						.each(obj_each);
					pop_scope();
					this.toChild('CatchBlock')
						.updateData({ scopeIndex: push_scope('catch') })
						.each(obj_each);
					pop_scope();
					break;
				case 'AsBlockFunction':
					this.updateData({ scopeIndex: push_scope('as') })
						.each(obj_each);
					pop_scope();
					break;
				default:
					this.each(obj_each);
				}
			};

		rho_obj.ast
			.updateData({ scopeIndex: 0 })
			.each(obj_each);

		/*
		function log_scope(sc, t)
		{
			var tab = require('./common.js').stringRepeat('  ', t);
			console.log(tab + sc.name);
			for (var i = 0; i < sc.children.length; i++)
				log_scope(sc.children[i], t + 1);
		}
		log_scope(rho_obj.scopeTree, 0); // */
	};
